package com.directv.drqp.init;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

import org.apache.commons.lang.StringUtils;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.directv.drqp.init.model.Attribute;

public class TreeBuilderImpl {

	private static final Logger logger = LoggerFactory
			.getLogger(TreeBuilderImpl.class);

	public HashMap<Class<?>, Map<String, Attribute<?>>> map = new HashMap<Class<?>, Map<String, Attribute<?>>>();
	public Map<String, Class> classToString = new HashMap<String, Class>();
	private boolean inited = false;

	/**
	 * objects with this package will be inspected as new nodes
	 */
	private static final String PACKAGE_OF_OBJECTS_TO_INSTPEC = "com.directv.";

	public TreeBuilderImpl() {
		// TODO Auto-generated constructor stub

		if (inited == true) {
			throw new IllegalStateException("already init dont run twice");
		}

		inited = true;

		Reflections reflections = new Reflections("my.project.prefix");

		Set<Class<?>> annotated = reflections
				.getTypesAnnotatedWith(XmlRootElement.class);
		Set<Class<?>> annotated2 = reflections
				.getTypesAnnotatedWith(XmlType.class);

		for (Class<?> class1 : annotated2) {
			buildTree(class1);
		}
		for (Class<?> class1 : annotated) {
			buildTree(class1);
		}
	}

	private synchronized <T> void buildTree(Class<T> c) {

		Map<String, Attribute<?>> attributeMap = new HashMap<String, Attribute<?>>();
		// we should only allow access through getters or public fields
		Field[] fields = c.getDeclaredFields();
		for (Field field : fields) {
			Method method = null;

			try {
				method = c.getMethod("get"
						+ StringUtils.capitalize(field.getName()));
			} catch (NoSuchMethodException e) {
				try {
					method = c.getMethod("is"
							+ StringUtils.capitalize(field.getName()));
				} catch (NoSuchMethodException e1) {
					logger.debug(
							"no field found for this method {} ,  so its not a javabean compileant getter",
							field.getName());
				}
			}
			if (method == null) {
				continue;
			}
			Attribute<T> a = buildAttribute(c, method);
			if (a != null) {
				attributeMap.put(a.getName(), a);
				if (a.isTraversable()) {
					buildTree(a.getType());
				}
			}
		}
		if (!attributeMap.isEmpty()) {
			map.put(c, attributeMap);
			String classElementName = null;
			XmlRootElement xmlRootElement = c
					.getAnnotation(XmlRootElement.class);
			if (xmlRootElement == null) {
				XmlType xmlType = c.getAnnotation(XmlType.class);
				if (xmlType == null) {
					classElementName = c.getSimpleName();
				} else {
					classElementName = xmlType.name();
				}
			} else {
				classElementName = xmlRootElement.name();
			}

			classToString.put(classElementName, c);
		}

		return;
	}

	/**
	 * FIXME this should be abstract or part of the interface we need to support
	 * when there is a public field also, but that is step 2
	 * 
	 * @return
	 */
	protected <T> Attribute<T> buildAttribute(Class<T> cc, Method method) {

		// Class<?> returnType = method.getReturnType();
		Type genericReturnType = method.getGenericReturnType();
		Class<?> returnType = null;

		boolean isCollection = false;
		if (Collection.class.isAssignableFrom(method.getReturnType())) {
			isCollection = true;
		}

		if (genericReturnType instanceof ParameterizedType) {
			ParameterizedType parameterizedType = (ParameterizedType) genericReturnType;
			returnType = (Class<?>) parameterizedType.getActualTypeArguments()[0];
		} else if (genericReturnType instanceof Class<?>) {
			returnType = (Class<?>) genericReturnType;
		}

		Package _package = returnType.getPackage();
		boolean isTraversable = false;
		if (_package != null) {
			// if the package is null then this is a simple type
			if (_package.getName().startsWith(PACKAGE_OF_OBJECTS_TO_INSTPEC)) {
				isTraversable = true;
			}
		}
		// method.getAnnotation(annotationClass)

		Attribute<T> a = new Attribute<T>(cc, returnType,
				getAttributeName(method), method, null, isTraversable,
				isCollection);
		return a;
	}

	protected String getAttributeName(Method method) {
		String methodName = method.getName();
		if (methodName.startsWith("get")) {
			methodName = StringUtils.removeStart(methodName, "get");
		} else if (methodName.startsWith("is")) {
			methodName = StringUtils.removeStart(methodName, "is");
		}

		methodName = StringUtils.uncapitalize(methodName);
		Field f = null;
		try {
			f = method.getDeclaringClass().getDeclaredField(methodName);
		} catch (NoSuchFieldException noSuchFieldException) {
			logger.debug(
					"no field found for this method {} , using this name: {} , so its not a javabean compileant getter",
					method.getName(), method);
			return null;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		if (f == null) {
			logger.debug(
					"no field found for this method {} , using this name: {} , so its not a javabean compileant getter",
					method.getName(), method);
			return null;
		}

		// we gotta do all types of fancy stuff for arrays
		// f.getAnnotation(annotationClass)
		return methodName;
	}

}
